/*
 *
 * NMEA library
 * URL: http://nmea.sourceforge.net
 * Author: Tim (xtimor@gmail.com)
 * Licence: http://www.gnu.org/licenses/lgpl.html
 * $Id: parser.c 17 2008-03-11 11:56:11Z xtimor $
 *
 */

/**
 * \file parser.h
 */

#include "nmea/tok.h"
#include "nmea/parse.h"
#include "nmea/parser.h"
#include "nmea/context.h"

#include <string.h>
#include <stdlib.h>

typedef struct _nmeaParserNODE
{
  nmea_lookup_t* lookup;
  void *pack;
  struct _nmeaParserNODE *next_node;

} nmeaParserNODE;

/*
 * high level
 */

/**
 * \brief Initialization of parser object
 * @return true (1) - success or false (0) - fail
 */
int nmea_parser_init(nmeaPARSER *parser)
{
    int resv = 0;
    int buff_size = nmea_property()->parse_buff_size;

    NMEA_ASSERT(parser);

    if(buff_size < NMEA_MIN_PARSEBUFF)
        buff_size = NMEA_MIN_PARSEBUFF;

    memset(parser, 0, sizeof(nmeaPARSER));

    if(0 == (parser->buffer = malloc(buff_size)))
        nmea_error("Insufficient memory!");
    else
    {
        parser->buff_size = buff_size;
        resv = 1;
    }    

    return resv;
}

/**
 * \brief Destroy parser object
 */
void nmea_parser_destroy(nmeaPARSER *parser)
{
    NMEA_ASSERT(parser && parser->buffer);
    free(parser->buffer);
    nmea_parser_queue_clear(parser);
    memset(parser, 0, sizeof(nmeaPARSER));
}

/**
 * \brief Analysis of buffer and put results to information structure
 * @return Number of packets wos parsed
 */
int nmea_parse(    
    nmeaPARSER *parser,
    const char *buff, int buff_sz,
    nmeaINFO *info
    )
{
    int nread = 0;
    void *pack = 0;
    nmea_lookup_t *lookup = NULL;

    NMEA_ASSERT(parser && parser->buffer);

    nmea_parser_push(parser, buff, buff_sz);

    while (lookup = (nmea_lookup_t*)nmea_parser_pop(parser, &pack))
    {
        nread++;

        (*lookup->pfn_info)(pack, info);
        info->smask |= lookup->ptype;
        free(pack);
    }

    return nread;
}

/*
 * low level
 */

int nmea_parser_real_push(nmeaPARSER *parser, const char *buff, int buff_sz)
{
    int nparsed = 0, crc, sen_sz;
    nmeaParserNODE *node = 0;
    nmea_lookup_t *lookup;

    NMEA_ASSERT(parser && parser->buffer);

    /* clear unuse buffer (for debug) */
    /*
    memset(
        parser->buffer + parser->buff_use, 0,
        parser->buff_size - parser->buff_use
        );
        */

    /* add */
    if(parser->buff_use + buff_sz >= parser->buff_size)
        nmea_parser_buff_clear(parser);

    memcpy(parser->buffer + parser->buff_use, buff, buff_sz);
    parser->buff_use += buff_sz;

    /* parse */
    for(;;node = 0)
    {
        sen_sz = nmea_find_tail(
            (const char *)parser->buffer + nparsed,
            (int)parser->buff_use - nparsed, &crc);

        if(!sen_sz)
        {
            if(nparsed)
                memcpy(
                parser->buffer,
                parser->buffer + nparsed,
                parser->buff_use -= nparsed);
            break;
        }
        else if(crc >= 0)
        {
          lookup = nmea_lookuptab(
            (const char *)parser->buffer + nparsed + 1,
            parser->buff_use - nparsed - 1);

            if(0 == (node = malloc(sizeof(nmeaParserNODE))))
                goto mem_fail;

            node->pack = 0;

            if(0 == (node->pack = malloc(lookup->pack_size)))
              goto mem_fail;
            //node->packType = lookup->ptype;
            node->lookup = lookup;
            if (!(*lookup->pfn_parse)(
              (const char *)parser->buffer + nparsed,
              sen_sz, node->pack, lookup->phead))
            {
              free(node);
              node = 0;
            }

            if(node)
            {
                if(parser->end_node)
                    ((nmeaParserNODE *)parser->end_node)->next_node = node;
                parser->end_node = node;
                if(!parser->top_node)
                    parser->top_node = node;
                node->next_node = 0;
            }
        }

        nparsed += sen_sz;
    }

    return nparsed;

mem_fail:
    if(node)
        free(node);

    nmea_error("Insufficient memory!");

    return -1;
}

/**
 * \brief Analysis of buffer and keep results into parser
 * @return Number of bytes wos parsed from buffer
 */
int nmea_parser_push(nmeaPARSER *parser, const char *buff, int buff_sz)
{
    int nparse, nparsed = 0;

    do
    {
        if(buff_sz > parser->buff_size)
            nparse = parser->buff_size;
        else
            nparse = buff_sz;

        nparsed += nmea_parser_real_push(
            parser, buff, nparse);

        buff_sz -= nparse;

    } while(buff_sz);

    return nparsed;
}

/**
 * \brief Get type of top packet keeped into parser
 * @return nmea_lookup_t's pointer of packet
 * @see nmea_lookuptab
 */
void* nmea_parser_top(nmeaPARSER *parser)
{
  nmea_lookup_t *lookup = NULL;
  nmeaParserNODE *node = (nmeaParserNODE *)parser->top_node;

  NMEA_ASSERT(parser && parser->buffer);

  if(node)
    lookup = node->lookup;

  return lookup;
}

/**
 * \brief Withdraw top packet from parser
 * @return nmea_lookup_t's pointer of packet
 * @see nmea_lookuptab
 */
void* nmea_parser_pop(nmeaPARSER *parser, void **pack_ptr)
{
  nmea_lookup_t *lookup = NULL;
  nmeaParserNODE *node = (nmeaParserNODE *)parser->top_node;

  NMEA_ASSERT(parser && parser->buffer);

  if(node)
  {
    *pack_ptr = node->pack;
    //retval = node->packType;
    lookup = node->lookup;
    parser->top_node = node->next_node;
    if(!parser->top_node)
      parser->end_node = 0;
    free(node);
  }

  return lookup;
}

/**
 * \brief Get top packet from parser without withdraw
 * @return nmea_lookup_t's pointer of packet
 * @see nmea_lookuptab
 */
void* nmea_parser_peek(nmeaPARSER *parser, void **pack_ptr)
{
  nmea_lookup_t *lookup = NULL;
  nmeaParserNODE *node = (nmeaParserNODE *)parser->top_node;

  NMEA_ASSERT(parser && parser->buffer);

  if(node)
  {
    *pack_ptr = node->pack;
    lookup = node->lookup;
  }

  return lookup;
}

/**
 * \brief Delete top packet from parser
 * @return Deleted nmea_lookup_t's pointer of packet
 * @see nmea_lookuptab
 */
void* nmea_parser_drop(nmeaPARSER *parser)
{
  nmea_lookup_t *lookup = NULL;
  nmeaParserNODE *node = (nmeaParserNODE *)parser->top_node;

  NMEA_ASSERT(parser && parser->buffer);

  if(node)
  {
    if(node->pack)
      free(node->pack);
    lookup = node->lookup;
    parser->top_node = node->next_node;
    if(!parser->top_node)
      parser->end_node = 0;
    free(node);
  }

  return lookup;
}

/**
 * \brief Clear cache of parser
 * @return true (1) - success
 */
int nmea_parser_buff_clear(nmeaPARSER *parser)
{
    NMEA_ASSERT(parser && parser->buffer);
    parser->buff_use = 0;
    return 1;
}

/**
 * \brief Clear packets queue into parser
 * @return true (1) - success
 */
int nmea_parser_queue_clear(nmeaPARSER *parser)
{
    NMEA_ASSERT(parser);
    while(parser->top_node)
        nmea_parser_drop(parser);
    return 1;
}
